// winsnd.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"

#include <MMSystem.h>
#include <dsound.h>

#include <math.h>

#include "winsnd.h"

#include "kotori_app_win32_AudioManagerWin32.h"
#include "kotori_app_win32_ClipSoundContextWin32.h"

CAudioManager *g_pAudioManager;

CAudioManager::CAudioManager()
:m_hWnd(NULL)
,m_pDS(NULL)
,m_pDSBuf(NULL)
,m_fVolume(1.0)
,m_fPan(1.0)
,m_iSpeaker(1)
,m_fuck6(0)
{
	ZeroMemory(m_fuck5,sizeof(m_fuck5));
	m_fuck5[0]=64;
}

void CAudioManager::setFormat()
{
	WAVEFORMATEX wf={0};
	wf.cbSize=sizeof(wf);
	wf.wFormatTag=WAVE_FORMAT_PCM;
	wf.nChannels = 2;
	wf.nSamplesPerSec = 44100;
	wf.wBitsPerSample = 16;
	wf.nBlockAlign = 4;
	wf.nAvgBytesPerSec = 176400;
	m_pDSBuf->SetFormat(&wf);
}

JNIEXPORT jboolean JNICALL Java_kotori_app_win32_AudioManagerWin32_initNative
(JNIEnv *, jclass)
{
	CoInitialize(0);
	CAudioManager *pAudioManager=new CAudioManager();
	if(FAILED(DirectSoundCreate(NULL,&pAudioManager->m_pDS,NULL)))
	{
		delete pAudioManager;
		return FALSE;
	}

	DSBUFFERDESC bufdesc={0};
	bufdesc.dwSize=sizeof(bufdesc);
	bufdesc.dwFlags=DSBCAPS_PRIMARYBUFFER|DSBCAPS_CTRL3D|DSBCAPS_CTRLVOLUME;
	if(FAILED(pAudioManager->m_pDS->CreateSoundBuffer(&bufdesc,&pAudioManager->m_pDSBuf,NULL)))
	{
		delete pAudioManager;
		return FALSE;
	}

	g_pAudioManager=pAudioManager;
	return TRUE;
}

JNIEXPORT void JNICALL Java_kotori_app_win32_AudioManagerWin32_updateScreenNative
(JNIEnv *, jclass)
{
	HWND hWnd=GetForegroundWindow();
	
	if(g_pAudioManager->m_hWnd!=hWnd)
	{
		if(g_pAudioManager->m_hWnd)
			g_pAudioManager->m_hWnd=NULL;
		if(hWnd)
		{
			if(SUCCEEDED(g_pAudioManager->m_pDS->SetCooperativeLevel(hWnd,DSSCL_PRIORITY)))
			{
				g_pAudioManager->m_hWnd=hWnd;
				g_pAudioManager->setFormat();
			}
		}
	}
}

JNIEXPORT void JNICALL Java_kotori_app_win32_AudioManagerWin32_setPanNative
(JNIEnv *, jclass, jfloat p)
{
	g_pAudioManager->m_fPan=p;
}

JNIEXPORT void JNICALL Java_kotori_app_win32_AudioManagerWin32_setVolumeNative
(JNIEnv *, jclass, jfloat v)
{
	g_pAudioManager->m_fVolume=v;
}

JNIEXPORT void JNICALL Java_kotori_app_win32_AudioManagerWin32_setSpeakerNative
(JNIEnv *, jclass, jint s)
{
	g_pAudioManager->m_iSpeaker=s;
}
JNIEXPORT jfloat JNICALL Java_kotori_app_win32_AudioManagerWin32_getPanNative
(JNIEnv *, jclass)
{
	return g_pAudioManager->m_fPan;
}
JNIEXPORT jfloat JNICALL Java_kotori_app_win32_AudioManagerWin32_getVolumeNative
(JNIEnv *, jclass)
{
	return g_pAudioManager->m_fVolume;
}

JNIEXPORT void JNICALL Java_kotori_app_win32_AudioManagerWin32_getListenerNative
(JNIEnv *env, jclass, jfloatArray o)
{
	jfloat tmp[]={
		g_pAudioManager->m_fuck5[1],
		g_pAudioManager->m_fuck5[2],
		g_pAudioManager->m_fuck5[3],
		g_pAudioManager->m_fuck5[7],
		g_pAudioManager->m_fuck5[8],
		g_pAudioManager->m_fuck5[9],
		g_pAudioManager->m_fuck5[10],
		g_pAudioManager->m_fuck5[11],
		g_pAudioManager->m_fuck5[12]
	};
	env->SetFloatArrayRegion(o,0,9,tmp);
}

JNIEXPORT jint JNICALL Java_kotori_app_win32_AudioManagerWin32_getSpeakerNative
(JNIEnv *, jclass)
{
	return g_pAudioManager->m_iSpeaker;
}

JNIEXPORT void JNICALL Java_kotori_app_win32_AudioManagerWin32_commitNative
(JNIEnv *, jclass)
{}

//////////////////////////////////////////////////////////////////////////

CClipSoundContext::CClipSoundContext()
{
ZeroMemory(this,sizeof(CClipSoundContext));
}

CClipSoundContext::~CClipSoundContext()
{
	if(m_buf1)
	{
		for(int i=0;i<m_bufnum;++i)
			if(m_buf1[i])
			{
				m_buf1[i]->Release();
				m_buf1[i]=NULL;
			}
			delete[] m_buf1;
	}
	if(m_buf)
		delete[] m_buf;
}

int CClipSoundContext::playing( CClipSound*sound )
{
	if(sound->m_id<0)
		return -1;
	if(m_buf2[sound->m_id]==sound)
	{
		DWORD status;
		m_buf1[sound->m_id]->GetStatus(&status);
		if(status & DSBSTATUS_PLAYING)
			return sound->m_id;
		m_buf2[sound->m_id]=NULL;
	}
	sound->m_id=-1;
	return -1;
}

BOOL CClipSoundContext::play( CClipSound* sound,int bufidx,BOOL loop )
{
	LPDIRECTSOUNDBUFFER buf=m_buf1[bufidx];
	if(m_buf2[bufidx])
	{
		buf->Stop();
		m_buf2[bufidx]->m_id=-1;
		m_buf2[bufidx]=NULL;
	}
	DWORD status;
	buf->GetStatus(&status);
	if(status & DSBSTATUS_BUFFERLOST )
	{
		buf->Restore();
		buf->GetStatus(&status);
		if(status & DSBSTATUS_BUFFERLOST)
			return FALSE;
		initBuffer(buf);
	}
	buf->SetCurrentPosition(0);
	buf->SetVolume(sound->getVolume(g_pAudioManager->m_fVolume));
	buf->SetFrequency(m_freq*sound->m_pitch);
	buf->SetPan(sound->getPan());
	if(FAILED(buf->Play(0,0,loop?DSBPLAY_LOOPING:0)))
	{
		return FALSE;
	}
	sound->m_id=bufidx;
	sound->m_loop=loop;
	m_buf2[bufidx]=sound;
	return TRUE;
}

int CClipSoundContext::getFreeBufferIndex( int rhint )
{
	if(m_bufnum<=0)
		return 0;

	for(int i=0;i<m_bufnum;++i)
	{
		if(m_buf2[i]==NULL)
			return i;
		if(playing(m_buf2[i])<0)
			return i;
	}

	if(rhint==1)
	{
		int n=0;
		int l=m_buf2[0]->m_fuck2;
		for(int i=1;i<m_bufnum;++i)
			if(m_buf2[i]->m_loop==FALSE &&
				m_buf2[i]->m_fuck2<l)
			{
				n=i;
				l=m_buf2[i]->m_fuck2;
			}
			return n;
	}
	return -1;
}

BOOL CClipSoundContext::initBuffer( LPDIRECTSOUNDBUFFER buf )
{
	void *audio=NULL;
	DWORD audioLen=0;
	if(SUCCEEDED(buf->Lock(0,m_length,&audio,&audioLen,0,0,0)))
	{
		CopyMemory(audio,m_pbuf,m_length);
		buf->Unlock(audio,audioLen,0,0);
		return TRUE;
	}
	return FALSE;
}

BOOL CClipSoundContext::createBuffer( LPDIRECTSOUNDBUFFER *buf,int bufnum,BYTE *pbuf,int length,WAVEFORMATEX *wf )
{
	void *audio=NULL;
	DWORD audioLen=0;

	DSBUFFERDESC bufdesc={0};
	bufdesc.dwSize=sizeof(bufdesc);
	bufdesc.dwBufferBytes=length;
	bufdesc.lpwfxFormat=wf;
	bufdesc.dwFlags=DSBCAPS_CTRLFREQUENCY|DSBCAPS_CTRLPAN|DSBCAPS_CTRLVOLUME;
	if(FAILED(g_pAudioManager->m_pDS->CreateSoundBuffer(&bufdesc,buf,NULL)) ||
		FAILED(buf[0]->Lock(0,length,&audio,&audioLen,0,0,0)))
		return FALSE;

	int len=audioLen;
	if(len>length)
		len=length;
	CopyMemory(audio,pbuf,len);
	buf[0]->Unlock(audio,audioLen,0,0);
	for(int i=1;i<bufnum;++i)
		if(FAILED(g_pAudioManager->m_pDS->DuplicateSoundBuffer(buf[0],buf+i)))
			return FALSE;

	return TRUE;
}

BOOL CClipSoundContext::init( float freq, int samplebits, int channel, void* wave,int length, int algo,int bufnum )
{
	m_bufnum=bufnum;
	m_buf1=new LPDIRECTSOUNDBUFFER[bufnum];
	m_buf2=new CClipSound*[bufnum];
	for(int i=0;i<bufnum;++i)
	{
		m_buf1[i]=NULL;
		m_buf2[i]=NULL;
	}
	m_buf=new BYTE[length];
	CopyMemory(m_buf,wave,length);
	m_freq=freq;
	m_algo=algo;
	m_samplebits=samplebits;
	m_channel=channel;
	m_pbuf=m_buf;
	m_length=length;
	if(m_algo)
		return TRUE;

	WAVEFORMATEX wf={0};
	wf.cbSize=sizeof(wf);
	wf.wFormatTag = WAVE_FORMAT_PCM;
	wf.nChannels = m_channel;
	wf.nSamplesPerSec = m_freq;
	wf.wBitsPerSample = m_samplebits;
	wf.nBlockAlign = m_channel * m_samplebits / 8;
	wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
	return createBuffer(m_buf1,m_bufnum,m_pbuf,m_length,&wf);
}

JNIEXPORT jlong JNICALL Java_kotori_app_win32_ClipSoundContextWin32_initNative
(JNIEnv *env, jclass, jfloat freq, jint	samplebits, jint channel, jbyteArray wave, jint lenght, jint algo, jint bufnum)
{
	void *pwave=env->GetPrimitiveArrayCritical(wave,NULL);
	if(pwave==NULL)
		return 0;

	CClipSoundContext *cont=new CClipSoundContext();
	BOOL ret=cont->init(freq,samplebits,channel,pwave,lenght,algo,bufnum);
	env->ReleasePrimitiveArrayCritical(wave,pwave,JNI_ABORT);
	if(!ret)
	{
		delete cont;
		cont=NULL;
	}
	return (jlong)cont;
}

JNIEXPORT void JNICALL Java_kotori_app_win32_ClipSoundContextWin32_disposeNative
(JNIEnv *, jclass, jlong c)
{
	CClipSoundContext *cont=reinterpret_cast<CClipSoundContext *>(c);
	if(cont)
		delete cont;
}

CClipSound::CClipSound(CClipSoundContext *cont,float volume,float pan,float pitch)
	:m_cont(cont)
	,m_id(-1)
	,m_fuck2(0)
	,m_volume(volume/100.0)
	,m_pan(pan)
{
	if(pitch>2.0)
		pitch=2.0;
	else if(pitch<0.5)
		pitch=0.5;
	m_pitch=pitch;
}
void CClipSound::setPitch(float pitch)
{
	if(pitch>2.0)
		pitch=2.0;
	else if(pitch<0.0)
		pitch=0.0;
	m_pitch=pitch;
	if(m_id>=0 && m_id<m_cont->m_bufnum)
		m_cont->m_buf1[m_id]->SetFrequency(m_cont->m_freq*m_pitch);
}

void CClipSound::setVolume(float volume)
{
	m_volume=volume/100.0;
	if(m_id>=0 && m_id<m_cont->m_bufnum)
		m_cont->m_buf1[m_id]->SetVolume(getVolume(g_pAudioManager->m_fVolume));
}
int CClipSound::getVolume(float volume)
{
	if(m_volume==0.0 || volume==0.0)
		return -10000;
	
	return log10(volume*m_volume)*20.0*100.0;
}
void CClipSound::setPan(float pan)
{
	m_pan=pan;
	if(m_id>=0 && m_id<m_cont->m_bufnum)
		m_cont->m_buf1[m_id]->SetPan(getPan());
}
int CClipSound::getPan()
{
	float pan=m_pan+m_pan-1.0;
	if(pan==0.0)
		return 0;
	if(pan>=0.5)
	{
		float t=1.0-pan;
		if(t==0.0)
			return 10000;
		return -(int)(log10(t)*20.0*100.0);
	}

	float t=pan+1.0;
	if(t==0.0)
		return -10000;
	return (int)(log10(t)*20.0*100.0);
}

void CClipSound::stop()
{
	if ( m_id != -1 )
	{
		m_cont->m_buf1[m_id]->Stop();
		m_cont->m_buf2[m_id]=NULL;
		m_id=-1;
	}
}


JNIEXPORT jlong JNICALL Java_kotori_app_win32_ClipSoundContextWin32_createSoundNative
(JNIEnv *, jclass, jlong nativeref, jfloat volume, jfloat pan, jfloat pitch)
{
	return (jlong)new CClipSound(reinterpret_cast<CClipSoundContext*>(nativeref),
		volume,pan,pitch);
}

JNIEXPORT void JNICALL Java_kotori_app_win32_ClipSoundContextWin32_disposeSoundNative
(JNIEnv *, jclass, jlong s)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		delete sound;
}

JNIEXPORT jboolean JNICALL Java_kotori_app_win32_ClipSoundContextWin32_playNative
(JNIEnv *, jclass, jlong s, jint bufidx, jboolean loop)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		return sound->play(bufidx,loop);

	return FALSE;
}

JNIEXPORT jint JNICALL Java_kotori_app_win32_ClipSoundContextWin32_stopNative
(JNIEnv *, jclass, jlong s)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		sound->stop();

	return -1;
}

JNIEXPORT jint JNICALL Java_kotori_app_win32_ClipSoundContextWin32_playingNative
(JNIEnv *, jclass, jlong s)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		return sound->playing();

	return -1;
}

JNIEXPORT jint JNICALL Java_kotori_app_win32_ClipSoundContextWin32_getFreeBufferIndexNative
(JNIEnv *, jclass, jlong c, jint rhint)
{
	CClipSoundContext *cont=reinterpret_cast<CClipSoundContext *>(c);
	if(cont)
		cont->getFreeBufferIndex(rhint);

	return -1;
}

JNIEXPORT void JNICALL Java_kotori_app_win32_ClipSoundContextWin32_setPanNative
(JNIEnv *, jclass, jlong s, jfloat pan)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		sound->setPan(pan);
}

JNIEXPORT void JNICALL Java_kotori_app_win32_ClipSoundContextWin32_setVolumeNative
(JNIEnv *, jclass, jlong s, jfloat v)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		sound->setVolume(v);
}

JNIEXPORT void JNICALL Java_kotori_app_win32_ClipSoundContextWin32_setPitchNative
(JNIEnv *, jclass, jlong s, jfloat pitch)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		sound->setPitch(pitch);
}

JNIEXPORT jfloat JNICALL Java_kotori_app_win32_ClipSoundContextWin32_getPanNative
(JNIEnv *, jclass, jlong s)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		return sound->m_pan;
	return 0.0;
}

JNIEXPORT jfloat JNICALL Java_kotori_app_win32_ClipSoundContextWin32_getVolumeNative
(JNIEnv *, jclass, jlong s)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		return sound->m_volume;
	return 0.0;
}

JNIEXPORT jfloat JNICALL Java_kotori_app_win32_ClipSoundContextWin32_getPitchNative
(JNIEnv *, jclass, jlong s)
{
	CClipSound *sound=reinterpret_cast<CClipSound *>(s);
	if(sound)
		return sound->m_pitch;
	return 0.0;
}
